package il.technion.cs236369.webserver;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;

/**
 * Thread that reads sockets from the socket queue, creates request object and puts it in the 
 * requests queue.
 * @author Yuval and Eddie
 *
 */
public class SocketReaderThread extends Thread {
	private Socket socket;          //the socket taken from the queue
	private WebServer webServer;    //this is received from the webServer itself

	/**
	 * Read a socket from the queue and build a request object.
	 */
	@Override
	public void run(){
		try{
			DefaultHttpServerConnection conn = null;
			BasicHttpParams params = new BasicHttpParams();
			HttpEntity entity = null;
			boolean isSocketTaken = false;          //True if a socket was taken from the queue successfully
			//but isn't fully handled yet. needed for exceptions.
			while(true){
				try {
					conn = new DefaultHttpServerConnection();
					isSocketTaken = false;

					//take a socket from the queue. blocks if there is no socket in the queue
					SocketWrapper socketWrapper = webServer.takeSocketFromQueue();
					isSocketTaken = true;
					socket = socketWrapper.getSocket();

					//connect to the socket, then take the entity if there is one
					conn.bind(socket, params);
					HttpRequest httpRequest = conn.receiveRequestHeader();
					if (httpRequest instanceof HttpEntityEnclosingRequest){
						conn.receiveRequestEntity((HttpEntityEnclosingRequest)httpRequest);
						entity = ((HttpEntityEnclosingRequest)httpRequest).getEntity();
					}

					//build request object
					Request request = new Request(webServer,
							conn,httpRequest.getRequestLine().getMethod(),
							getAbsouteURL(httpRequest.getRequestLine().getUri()),
							entity,httpRequest.getAllHeaders());
					insertParametersToRequest(request, httpRequest, entity);

					//insert request to the requests queue. blocks if there is no space in the queue
					webServer.putRequestInQueue(request);

				} catch(MalformedURLException e){
					//URL in request is not a valid URL
					e.printStackTrace();
					webServer.sendErrorResponse(conn, 400, "Bad Request", "Malformed URL", true);
				} catch (InterruptedException e) {
					e.printStackTrace();
					if(isSocketTaken){
						//if true- the problem was with inserting the request to the queue
						//otherwise- the problem was taking a socket from the queue- ignore
						webServer.sendErrorResponse(conn, 500, "Internal Server Error", 
								"Problem puting request object in requests queue",true);
					}                                               
				} catch(IOException e){
					//ignore
					e.printStackTrace();
					try{conn.close();}catch(Exception e2){e2.printStackTrace();}
				} catch(HttpException e){
					//ignore
					e.printStackTrace();
					try{conn.close();}catch(Exception e2){e2.printStackTrace();}
				}
			}
		} catch (Exception e){e.printStackTrace();}
	}

	public void setWebServer(WebServer webserver){
		this.webServer = webserver;
	}

	/*
	 * parses the uri string and returns the absolute requested path in the server's file system
	 */
	private String getAbsouteURL(String uri) throws MalformedURLException{
		uri.replace("http://", "");
		String[] splittedURL = uri.split("/",2);                //split by only the first /
		// cell 1 contains the absolute path with the parameters. we have to remove them
		String relativeURL[] = splittedURL[1].split("\\?", 2);
		// now cell 0 contains the relative url in the server's file system. we add base dir 
		return webServer.getBaseDir() + relativeURL[0];
	}

	/*
	 * Parses the URL in httpRequest and inserts the parameters to the request object
	 * entity is needed only in POST requests. can be null in GET requests
	 */
	private void insertParametersToRequest(Request request,HttpRequest httpRequest, HttpEntity entity) throws MalformedURLException, IOException{
		//if this is POST method, parameters are in the entity
		try{
			List<NameValuePair> parametersList;
			if (httpRequest.getRequestLine().getMethod().equalsIgnoreCase("POST")){
				parametersList = URLEncodedUtils.parse(entity);
			}
			else{
				parametersList = URLEncodedUtils.parse(new URI(httpRequest.getRequestLine().getUri()),"UTF-8");
			}
			for (NameValuePair nvp : parametersList){
				request.insertParameter(nvp.getName(), nvp.getValue());
			}

		} catch (URISyntaxException e){
			e.printStackTrace();
			throw new MalformedURLException();
		}
	}
}
